﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Passwords.Utils
{
    [Flags]
    public enum Complexity
    {
        None = 0,
        Digits = 1,
        Lowercase = 2,
        Uppercase = 4,
        Complex = 8
    }

    public enum PasswordStrengthLevel
    {
        Weak=0,
        Medium=1,
        Good=2,
        Strong=3
    }
    public static class PasswordUtility
    {
        const string lower = "abcdefghijklmnopqrstuvwxyz";
        const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const string digits = "0123456789";
        const string complex = ".,;;:#*-+*!%&()?<>=";

        public static int GetStrength(string password)
        {
            if (password == null) password = string.Empty;
            int charSet = getCharSetUsed(password);
            int len = password.Length;
            double strength = Math.Log(Math.Pow(charSet, len)) / Math.Log(2d);
            return (int)(strength + 0.5d);
        }

        public static PasswordStrengthLevel GetLevelFromValue(int strength)
        {
            if (strength < 32) return PasswordStrengthLevel.Weak;
            if (strength < 64) return PasswordStrengthLevel.Medium;
            if (strength < 128) return PasswordStrengthLevel.Good;
            return PasswordStrengthLevel.Strong;
        }



        private static int getCharSetUsed(string pass)
        {
            int ret = 0;

            if (containsNumbers(pass))
            {
                ret += 10;
            }

            if (containsLowerCaseChars(pass))
            {
                ret += 26;
            }

            if (containsUpperCaseChars(pass))
            {
                ret += 26;
            }

            if (containsPunctuation(pass))
            {
                ret += 31;
            }

            return ret;
        }

        private static bool containsNumbers(string str)
        {
            Regex pattern = new Regex(@"[\d]");
            return pattern.IsMatch(str);
        }

        private static bool containsLowerCaseChars(string str)
        {
            Regex pattern = new Regex("[a-z]");
            return pattern.IsMatch(str);
        }

        private static bool containsUpperCaseChars(string str)
        {
            Regex pattern = new Regex("[A-Z]");
            return pattern.IsMatch(str);
        }

        private static bool containsPunctuation(string str)
        {
            // regular expression include _ as a valid char for alphanumeric.. 
            // so we need to explicity state that its considered punctuation.
            Regex pattern = new Regex(@"[\W|_]");
            return pattern.IsMatch(str);
        }


        private static bool HasNumbers(Complexity c)
        {
            return (c & Complexity.Digits) != 0;
        }

        private static bool HasUpper(Complexity c)
        {
            return (c & Complexity.Uppercase) != 0;
        }

        private static bool HasLower(Complexity c)
        {
            return (c & Complexity.Lowercase) != 0;
        }


        private static bool HasComplex(Complexity c)
        {
            return (c & Complexity.Complex) != 0;
        }

        public static string GeneratePassword(Complexity complexity, int numChars)
        {
            byte[] randomNumbers = new byte[numChars];
            System.Security.Cryptography.RNGCryptoServiceProvider provider = new System.Security.Cryptography.RNGCryptoServiceProvider();
            provider.GetBytes(randomNumbers);

            string map = GetCharMap(complexity);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < numChars; i++)
            {
                int p = randomNumbers[i] % map.Length;
                char c = map[p];
                sb.Append(c);
            }
            string s = sb.ToString();
            if (randomNumbers.Length > 3 && complexity == Complexity.Complex && s.IndexOfAny(complex.ToArray()) < 0)
            {
                provider.GetBytes(randomNumbers);
                int index = randomNumbers[0] % s.Length;
                sb[index] = complex[randomNumbers[1] % complex.Length];
            }
            if (randomNumbers.Length > 2 && HasNumbers(complexity) && s.IndexOfAny(digits.ToArray()) < 0)
            {
                provider.GetBytes(randomNumbers);
                int index = randomNumbers[0] % s.Length;
                sb[index] = digits[randomNumbers[1] % digits.Length];
            }
            s = sb.ToString();
            return s;
        }

        private static void AddChars(int s, int l, byte[] randomNumbers, StringBuilder sb, string complex)
        {
            throw new NotImplementedException();
        }

        private static string GetCharMap(Complexity complexity)
        {
            StringBuilder sb = new StringBuilder();
            if (HasNumbers(complexity)) sb.Append(digits);
            if (HasLower(complexity)) sb.Append(lower + lower);
            if (HasUpper(complexity)) sb.Append(upper + upper);
            if (HasComplex(complexity)) sb.Append(complex);

            return sb.ToString();
        }

    }
}
